package handler

import (
	"context"
	blobpb "coolcar/proto/blob_out/v1"
	"coolcar/srvs/blob_srv/dao"
	"io"
	"io/ioutil"
	"net/http"
	"time"

	"go.mongodb.org/mongo-driver/mongo"

	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"

	"go.uber.org/zap"
)

type Service struct {
	Mongo   *dao.Mongo
	Logger  *zap.Logger
	Storage Storage
	blobpb.UnimplementedBlobServiceServer
}

type Storage interface {
	GetSignUrl(c context.Context, method string, filePath string, timeOut time.Duration) (signedURL string, err error)
	Get(c context.Context, filePath string) (io.ReadCloser, error)
}

func (s Service) CreateBlob(c context.Context, request *blobpb.CreateBlobRequest) (*blobpb.CreateBlobResponse, error) {
	br, err := s.Mongo.CreateBlob(c, request.AccountId)
	if err != nil {
		s.Logger.Error("can not create blob", zap.Error(err))
		return nil, status.Error(codes.Internal, "")
	}

	signedURL, err := s.Storage.GetSignUrl(c, http.MethodGet, br.Path, secToDuration(request.UploadUrlTimeoutSec))
	if err != nil {
		return nil, status.Error(codes.Aborted, err.Error())
	}

	return &blobpb.CreateBlobResponse{
		Id:        br.ID.Hex(),
		UploadUrl: signedURL,
	}, nil
}

func (s Service) GetBlob(c context.Context, request *blobpb.GetBlobRequest) (*blobpb.GetBlobResponse, error) {
	br, err := s.getBlobRecord(c, request.Id)
	if err != nil {
		return nil, status.Error(codes.Internal, "")
	}

	closer, err := s.Storage.Get(c, br.Path)
	if closer != nil {
		defer closer.Close()
	}
	if err != nil {
		return nil, status.Error(codes.Aborted, err.Error())
	}

	b, err := ioutil.ReadAll(closer)
	if err != nil {
		return nil, status.Errorf(codes.Aborted, "cannot read from response: %v", err)
	}

	return &blobpb.GetBlobResponse{
		Data: b,
	}, nil
}

func (s Service) GetBlobURL(c context.Context, request *blobpb.GetBlobURLRequest) (*blobpb.GetBlobURLResponse, error) {
	br, err := s.getBlobRecord(c, request.Id)
	if err != nil {
		return nil, status.Error(codes.Internal, "")
	}

	signedURL, err := s.Storage.GetSignUrl(c, http.MethodGet, br.Path, secToDuration(request.TimeoutSec))
	if err != nil {
		return nil, status.Error(codes.Aborted, err.Error())
	}

	return &blobpb.GetBlobURLResponse{
		Url: signedURL,
	}, nil
}

func (s *Service) getBlobRecord(c context.Context, blobID string) (*dao.BlobRecord, error) {
	br, err := s.Mongo.GetBlob(c, blobID)
	if err == mongo.ErrNoDocuments {
		return nil, status.Error(codes.NotFound, "")
	}
	if err != nil {
		return nil, status.Error(codes.InvalidArgument, err.Error())
	}

	return br, nil
}

func secToDuration(sec int32) time.Duration {
	return time.Duration(sec) * time.Second
}
